/* Main loop for NTP server */

/* Copyright (C) 2009-2012 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <string.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <avr/sleep.h>
#include <avr/wdt.h>

#include "global.h"
#include <util/delay.h>
#include <stdlib.h>

#include "serial.h"
#include "ether.h"
#include "rtc.h"
#include "gps.h"
#include "console.h"
#include "encx24j600.h"
#include "ip.h"
#include "tcp.h"
// #include "at25df.h"
// #include "led.h"
#include "dhcpc.h"
#include "arp.h"
#include "udp.h"
#include "ds3234.h"
#include "spi.h"
#include "tftp.h"
#include "fw_update.h"

/* this is the signature for a valid eeprom image */
uint16_t EEMEM eepromsig = 0xd027; 

/* check the validity of the EEPROM */
void check_eeprom(void) {
	if (eeprom_read_word(&eepromsig) != EEPROM_SIG) {
		/* not correct, nuke things back to defaults */

#ifdef IP_H
		ip_eeprom_init();
#endif

#ifdef CONSOLE_H
		console_eeprom_init();
#endif

#ifdef ENCX24J600_H
		encx24j600_eeprom_init();
#endif

		eeprom_write_word(&eepromsig,EEPROM_SIG);
		serial0_tx_PGM(PSTR("eeprom: setting defaults\r\n"));
	}
}

int main(void) {
	/* used by idle detection */
	uint8_t task_idle;

	cli(); /* disable all interrupts, we will enable them later */

	/* switch to 32MHz internal RC */
	OSC.CTRL = OSC_RC32MEN_bm; /* fire up internal 32MHz clock */
	while (!(OSC.STATUS & OSC_RC32MRDY_bm)); /* wait for it to stablise */
	CCP = CCP_IOREG_gc; /* allow protection IO reg change, next step must occur within 4 instructions */
	CLK.CTRL = CLK_SCLKSEL_RC32M_gc; /* use internal 32MHz clock */

	/* STALL */

	/* wipe flags */
	flag_stat = 0;
	flag_clock = 0;
	flag_gps = 0;
	flag_ether = 0;
	flag_ip = 0;
	flag_serial = 0;

	/* power down all modules, they can be enabled on a per-driver basis */
	/* this breaks the datasheet rules by writing 1 to everything.. fixme? */
	//PR.PRPA = 0xff;
	//PR.PRPB = 0xff;
	//PR.PRPC = 0xff;
	//PR.PRPD = 0xff;
	//PR.PRPE = 0xff;
	//PR.PRPF = 0xff;

	/* we are good to enable interrupts globally. they still need to be
     enabled per-subsystem anyway */
	sei();

#ifdef CONSOLE_H
	/* init console */
	console_init();
#endif

	/* report reset reason */
	serial0_tx_PGM(PSTR("reset: "));
	serial0_tx_hex(RST.STATUS);
	serial0_tx_cr();
	RST.STATUS = 0x3f; /* and then clean it up */

	/* check eeprom */
	check_eeprom(); /* nukes eeprom settings to default if invalid */

#ifdef CONSOLE_H
	/* okay, safe to init the hostname from eeprom now */
	hostname_init();
#endif

	/* reset counters */
	global_counters.uptime = 0;
#ifdef CONSOLE_H
	clear_counters(); /* does not include uptime */
#endif

#ifdef LED_H
	/* indicate no lock, no alignment */
	led_init();
	LED_RED;
#endif

	/* show some LEDs */
	PORTC.DIRSET = 0xff;
	PORTC.OUTSET = PIN0_bm;

#ifdef RTC_H
	rtc_init();
#endif

#ifdef SPI_H
	/* ensure !GPSRESET (which is externally pulled down) does not interfere with SPI */
	PORTD.DIRSET = PIN4_bm; /* !GPSRESET is output */
	spi_init();
#endif

#ifdef DS3234_H
	/* DS3234 init must happen before flash init, or CS will be aserted on 1.4 */
	ds3234_init();
#endif

#ifdef GPS_H
	gps_init();
#endif

	//serial1_init();
	//PORTD.DIRSET = PIN4_bm; /* !RESET on GPS */
	//PORTD.OUTSET = PIN4_bm; /* drive high so we get some data out of it */

#ifdef ENCX24J600_H
	/* initalise the ethernet chip */
	encx24j600_init();
#endif

#ifdef IP_H
	ip_init(); /* inisalise IP stack defaults */
#endif

#ifdef DHCPC_H
	if (flag_ip & FLAG_IP_DHCP) {
		dhcpc_init();
	}
#endif

#ifdef AT25DF_H
	/* initalise and identify SPI flash chip attached */
	/* caution: must be called *after* ds3234 init on 1.4 */
	at25df_init();

//	{
//		/* a selection of secure random numbers LOL! */
//		uint8_t x[10] = { 98, 80, 60, 94, 187, 80, 110, 187, 149, 35 };
//		uint8_t y[10], n;
//
//		at25df_write_array(0x0, 10, x);
//		at25df_read_array(0x0, 10, y);
//
//		for (n = 0; n < 9; n++) {
//			if (x[n] != y[n]) {
//				serial0_tx_PGM(PSTR("at25df write/read test failed at "));
//				serial0_tx_dec(n);
//				serial0_tx_cr();
//				break;
//			}
//		}
//		serial0_tx_PGM(PSTR("at25df test complete\r\n"));
//	}

#endif

	/* display the hostname */
	serial0_tx(hostname, hostname_len);
	serial0_tx_PGM(PSTR("# "));

#ifdef LED_H
	DDRD |= (1 << PORTD4) | (1 << PORTD5);
	PORTD &= ~(1 << PORTD5);
	PORTD &= ~(1 << PORTD4);
#endif

	/* prepare sleep mode, idle is just halt CPU, run everything else */
	SLEEP.CTRL |= SLEEP_SMODE_IDLE_gc;
	sleep_enable();

	task_idle = 0;

	/* enable the watchdog timer before entering the main loop */
	wdt_enable(WDT_PER_8KCLK_gc); /* 8s timeout */

	while(1) {

		//if (flag_gps & FLAG_GPS_PPS) {
			//serial0_tx_PGM(PSTR("gps: main loop got PPS flag\r\n"));
			//clear_flag(flag_gps,FLAG_GPS_PPS);
		//}

		/* tap the watchdog since we're still alive */
		wdt_reset();
	//	PORTC.OUTTGL = PIN0_bm;

//		task_idle++;

//		if (task_idle > 2) {
			/* nothing has run in a while, go to sleep */
			//_delay_ms(10); /* make sure the activity led is sufficiently long lit */
//			PORTC.OUTCLR = PIN2_bm;
//			sleep_cpu();
//			PORTC.OUTSET = PIN2_bm; /* we were woken up and back in main loop, light activity led */
//		}

#ifdef CONSOLE_H
		if (flag_serial & FLAG_SERIAL_S0_CMD) {
			clear_flag(flag_serial,FLAG_SERIAL_S0_CMD);
			console_process();
			task_idle = 0;
		}
#endif

		if (flag_serial & FLAG_SERIAL_S1_RX) {
#ifdef GPS_H
			gps_process();
			task_idle = 0;
#endif
		}

		if (flag_gps & FLAG_GPS_EVENT_TIME) {
			clear_flag(flag_gps,FLAG_GPS_EVENT_TIME);
#ifdef RTC_H
			rtc_update_s = rtc_s_gps;
			//clock_align();
#endif
#ifdef DS3234_H
			ds3234_align();
#endif
			task_idle = 0;
		}

#ifdef ENCX24J600_H
		/* handle interrupts from the Ethernet chip */
		if (flag_ether & FLAG_ETHER_INT) {
#ifdef DEBUG_ENCX24J600
			//			serial0_tx_PGM(PSTR("encx24j600: interrupt\r\n"));
#endif
			clear_flag(flag_ether,FLAG_ETHER_INT);
			encx24j600_interrupt();
			task_idle = 0;
		}

		/* process any ethernet frames waiting */
		if (flag_ether & FLAG_ETHER_RX) {
			clear_flag(flag_ether,FLAG_ETHER_RX);
			/* check we have waiting packets */
			if (encx24j600_rx_waiting()) {
				ether_process();
			}
			task_idle = 0;
		}
#endif

		/* handle link state changes, by tripping other code */
		if (flag_ether & FLAG_ETHER_LINK_EVENT) {
			clear_flag(flag_ether,FLAG_ETHER_LINK_EVENT);
			if (flag_ether & FLAG_ETHER_LINK) {
#ifdef DHCPC_H
				dhcpc_linkup();
#endif
#ifdef ARP_H
				/* clean out the ARP cache since we may have a new network */
				arp_init();
#endif
#ifdef TCP_H
				/* clear TCP connection table */
				tcp_init();
#endif
			} else {
#ifdef DHCPC_H
				dhcpc_linkdown();
#endif
			}
			task_idle = 0;
		}

		/* deal with 1Hz events */
		if (flag_clock & FLAG_CLOCK_TICK) {
			clear_flag(flag_clock,FLAG_CLOCK_TICK);

			if (flag_ip & FLAG_IP_DHCP) {
#ifdef DHCPC_H
				dhcpc_tick();
#endif
			}
#ifdef ARP_H
			arp_tick();
#endif
#ifdef TCP_H
			tcp_tick();
#endif
#ifdef TFTP_H
			tftp_tick();
#endif
#ifdef DEBUG_UDP_SEND
			if (rtc_s % 10 == 0 && (flag_ip & FLAG_IP_CONFIG)) {
				uint8_t pkt[32], addr[4] = {76,74,157,29};
				uint8_t n;

				for (n = 0; n < 32; n++) {
					pkt[n] = n;
				}
				udp_send(addr,1024,1024,pkt,32); /* generate some junk */
			}
#endif
			task_idle = 0;
		}

		/* check to see if we have a firmware upgrade required */
		if (flag_stat & FLAG_STAT_FW_UPGRADE) {
			fw_install();
		}

	}
}

